/*
 * libmad - MPEG audio decoder library
 * Copyright (C) 2000-2004 Underbit Technologies, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * $Id: minimad.c,v 1.4 2004/01/23 09:41:32 rob Exp $
 */

# include <stdio.h>
# include <unistd.h>
# include <sys/stat.h>
# include <sys/mman.h>

#include <sys/types.h>
#include <fcntl.h>

# include "mad.h"

#include <ncurses.h>
#include <stdlib.h>
#include <fftw3.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>

#include "alsa/asoundlib.h"

#include <math.h>

typedef unsigned char uint8_t;
typedef unsigned short uint16_t;

// 128点
#define N           256
// 最大幅度
#define MAX_LINE    40

// 输出到屏幕
#define RAW_DATA_OUT_PRINTF		0
// 输出到文件
#define RAW_DATA_OUT_FILE		0
// 显示频谱
#define RAW_DATA_OUT_SPECTRUM	1
// 播放
#define AUDIO_PLAY				1


#if RAW_DATA_OUT_SPECTRUM
// 使用ncurses显示
#define SHOW_ON_NCURSES			1
#endif


#if RAW_DATA_OUT_SPECTRUM
// 输入输出变量定义
fftw_complex *in, *out_new, *out_curr;
fftw_plan p;
#endif

#if AUDIO_PLAY
char *pcm_buffer;
snd_pcm_uframes_t frames;
snd_pcm_t *playback_handle;//PCM设备句柄pcm.h
#endif

static int decode(unsigned char const *, unsigned long);

// 显示频谱函数
void show_spectrum(void)
{
	uint8_t i, j;

	for(i = 0; i < MAX_LINE; ++i) {
		for(j = 0; j < N/2; ++j) {
			if(out_curr[j][1] >= (MAX_LINE-i))
				mvprintw(i, j, "-");
			else
				mvprintw(i, j, " ");
		}
	};
	
	// 将虚拟屏幕上的内容写到显示器上，并刷新
	refresh();
}

#if AUDIO_PLAY
int init_snd_card(void)
{
	int ret, dir = 0, size;
	unsigned int val;
	
	snd_pcm_uframes_t periodsize;
	snd_pcm_hw_params_t *hw_params;//硬件信息和PCM流配置
	
	//1. 打开PCM，最后一个参数为0意味着标准配置
	ret = snd_pcm_open(&playback_handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
	if (ret < 0) {
		perror("snd_pcm_open");
		exit(1);
	}
	
	//2. 分配snd_pcm_hw_params_t结构体
	ret = snd_pcm_hw_params_malloc(&hw_params);
	if (ret < 0) {
		perror("snd_pcm_hw_params_malloc");
		exit(1);
	}
	//3. 初始化hw_params
	ret = snd_pcm_hw_params_any(playback_handle, hw_params);
	if (ret < 0) {
		perror("snd_pcm_hw_params_any");
		exit(1);
	}
	//4. 初始化访问权限
	ret = snd_pcm_hw_params_set_access(playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
	if (ret < 0) {
		perror("snd_pcm_hw_params_set_access");
		exit(1);
	}
	//5. 初始化采样格式SND_PCM_FORMAT_U8,8位
	ret = snd_pcm_hw_params_set_format(playback_handle, hw_params, SND_PCM_FORMAT_S16_LE);
	if (ret < 0) {
		perror("snd_pcm_hw_params_set_format");
		exit(1);
	}
	
	//6. 设置采样率，如果硬件不支持我们设置的采样率，将使用最接近的
	val = 44100;
	ret = snd_pcm_hw_params_set_rate_near(playback_handle, hw_params, &val, &dir);
	if (ret < 0) {
		perror("snd_pcm_hw_params_set_rate_near");
		exit(1);
	}
	
	//7. 设置通道数量
	ret = snd_pcm_hw_params_set_channels(playback_handle, hw_params, 2);
	if (ret < 0) {
		perror("snd_pcm_hw_params_set_channels");
		exit(1);
	}
	
	/* Set period size to 32 frames. */
    frames = 1152;
    // periodsize = frames * 2 * 2;
    // ret = snd_pcm_hw_params_set_buffer_size_near(playback_handle, hw_params, &periodsize);
    // if (ret < 0) 
    // {
         // printf("Unable to set buffer size %li : %s\n", frames * 2, snd_strerror(ret));
         
    // }
	
    // periodsize /= 2;
    // ret = snd_pcm_hw_params_set_period_size_near(playback_handle, hw_params, &periodsize, 0);
    // if (ret < 0) 
    // {
        // printf("Unable to set period size %li : %s\n", periodsize,  snd_strerror(ret));
    // }
								  
	//8. 设置hw_params
	ret = snd_pcm_hw_params(playback_handle, hw_params);
	if (ret < 0) {
		perror("snd_pcm_hw_params");
		exit(1);
	}
	
	/* Use a buffer large enough to hold one period */
    //snd_pcm_hw_params_get_period_size(hw_params, &frames, &dir);

    size = frames * 4; /* 2 bytes/sample, 2 channels */
    pcm_buffer = (char *) malloc(size);
    printf("size = %d\n", size);
	
	return 0;
}
#endif

int main(int argc, char *argv[])
{
	struct stat stat;
	void *fdm;
	int fd;

	if (argc != 2)
		return 1;
	
#if RAW_DATA_OUT_SPECTRUM
	// fft变换内存分配
	in = (fftw_complex *)fftw_malloc(sizeof(fftw_complex) * N);
	out_curr = (fftw_complex *)fftw_malloc(sizeof(fftw_complex) * N);
	out_new = (fftw_complex *)fftw_malloc(sizeof(fftw_complex) * N);
	if(in == NULL || out_curr == NULL || out_new == NULL)
		return 1;

#ifdef SHOW_ON_NCURSES
    initscr();          /* 初始化，进入NCURSES模式 */
#endif
#endif

	// 打开mp3文件
	fd = open (argv[1], O_RDONLY);
	if(fd < 0)
		return 2;
	
#if AUDIO_PLAY
	if(init_snd_card() != 0) {
		printf("snd init error!\n");
		return 3;
	}
#endif

	int ret = fstat(fd, &stat);	// STDIN_FILENO
	if (ret == -1 || stat.st_size == 0) {
		printf("ret:%d, %ld\n", ret, stat.st_size);
		return 2;
	}

	fdm = mmap(0, stat.st_size, PROT_READ, MAP_SHARED, fd, 0);
	if (fdm == MAP_FAILED)
		return 3;

	decode(fdm, stat.st_size);
	
	printf("play over.\n");

#if RAW_DATA_OUT_SPECTRUM
#ifdef SHOW_ON_NCURSES
	// 退出NCURSES模式
    endwin();
#endif
	fftw_free(in);
	fftw_free(out_curr);
	fftw_free(out_new);
#endif

#if AUDIO_PLAY
	//10. 关闭PCM设备句柄
	snd_pcm_close(playback_handle);
	
	// 释放内存
	free(pcm_buffer);
	pcm_buffer = NULL;
#endif

	if (munmap(fdm, stat.st_size) == -1)
		return 4;

	return 0;
}

/*
 * This is a private message structure. A generic pointer to this structure
 * is passed to each of the callback functions. Put here any data you need
 * to access from within the callbacks.
 */
struct buffer {
	unsigned char const *start;
	unsigned long length;
};

/*
 * This is the input callback. The purpose of this callback is to (re)fill
 * the stream buffer which is to be decoded. In this example, an entire file
 * has been mapped into memory, so we just call mad_stream_buffer() with the
 * address and length of the mapping. When this callback is called a second
 * time, we are finished decoding.
 */
static enum mad_flow input(void *data, struct mad_stream *stream)
{
	struct buffer *buffer = data;

	if (!buffer->length)
		return MAD_FLOW_STOP;

	mad_stream_buffer(stream, buffer->start, buffer->length);
	buffer->length = 0;

	return MAD_FLOW_CONTINUE;
}

static enum mad_flow header_info(void *data, struct mad_header const *header)
{
	//printf(" samplerate:%d\n channel:%d\n bitrate:%ld\n layer:%d\n duration:%ld'%lu''\n", header->samplerate, header->mode, header->bitrate, header->layer, header->duration.seconds, header->duration.fraction);
	
	return MAD_FLOW_CONTINUE;
}

/*
 * The following utility routine performs simple rounding, clipping, and
 * scaling of MAD's high-resolution samples down to 16 bits. It does not
 * perform any dithering or noise shaping, which would be recommended to
 * obtain any exceptional audio quality. It is therefore not recommended to
 * use this routine if high-quality output is desired.
 */
static inline signed int scale(mad_fixed_t sample)
{
	/* round */
	sample += (1L << (MAD_F_FRACBITS - 16));

	/* clip */
	if (sample >= MAD_F_ONE)
		sample = MAD_F_ONE - 1;
	else if (sample < -MAD_F_ONE)
		sample = -MAD_F_ONE;

	/* quantize */
	return sample >> (MAD_F_FRACBITS + 1 - 16);
}

/*
 * This is the output callback function. It is called after each frame of
 * MPEG audio data has been completely decoded. The purpose of this callback
 * is to output (or play) the decoded PCM audio.
 */
static enum mad_flow output(void *data, struct mad_header const *header, struct mad_pcm *pcm)
{
	int ret;
	static int has_out_info = 0;
	
	unsigned int nchannels, nsamples;
	mad_fixed_t const *left_ch, *right_ch;

	/* pcm->samplerate contains the sampling frequency */
	nchannels = pcm->channels;
	nsamples  = pcm->length;
	left_ch   = pcm->samples[0];
	right_ch  = pcm->samples[1];

#if RAW_DATA_OUT_PRINTF
	//if(!has_out_info) 
	{
		nsamples_count += nsamples;
		printf("chan:%d, samples:%d, l_ch:%d, r_ch:%d, duration:%d:%d\n", nchannels, nsamples_count, left_ch, right_ch, header->duration.seconds, header->duration.fraction);
		return MAD_FLOW_CONTINUE;
	}
#endif
	
	int i = 0, w_count = 0;
	while (nsamples--) {
		signed int sample;

		/* output sample(s) in 16-bit signed little-endian PCM */
		sample = scale(*left_ch++);

#if RAW_DATA_OUT_PRINTF
		printf("%02x %02x ", ((sample >> 0) & 0xff), ((sample >> 8) & 0xff));
#elif RAW_DATA_OUT_FILE
		putchar((sample >> 0) & 0xff);
		putchar((sample >> 8) & 0xff);
#elif RAW_DATA_OUT_SPECTRUM
		//printf("%d ", sample);
		// fft显示
		if(w_count < N) {
			in[w_count][0] = sample;
			in[w_count][1] = 0;
			++w_count;
		}
#endif

#if AUDIO_PLAY
		// 保存数据
		pcm_buffer[i++] = ((sample >> 0) & 0xff);
		pcm_buffer[i++] = ((sample >> 8) & 0xff);
#endif

		if (nchannels == 2) {
			sample = scale(*right_ch++);
#if RAW_DATA_OUT_PRINTF
			printf("%02x %02x ", ((sample >> 0) & 0xff), ((sample >> 8) & 0xff));
#elif RAW_DATA_OUT_FILE
			putchar((sample >> 0) & 0xff);
			putchar((sample >> 8) & 0xff);
			
#else
		// Nothing to do!
#endif

#if AUDIO_PLAY
			// 保存数据
			pcm_buffer[i++] = ((sample >> 0) & 0xff);
			pcm_buffer[i++] = ((sample >> 8) & 0xff);
#endif
		}
	}

#if RAW_DATA_OUT_SPECTRUM
	p = fftw_plan_dft_1d(N, in, out_new, FFTW_FORWARD, FFTW_ESTIMATE);
	fftw_execute(p);
	fftw_destroy_plan(p);
	fftw_cleanup();

	double temp = 0;
	for(i = 0; i < N; ++i) {
		if(out_new[i][1] < 0)
			out_new[i][1] = (-out_new[i][1]);
		//printf("%f ", out_new[i][1]);
		temp = sqrt(out_new[i][1])/N/3*MAX_LINE;
		//printf("%f ", temp);

		if(temp >= out_curr[i][1]) {
			out_curr[i][1] = temp;
		}
		else {
			--out_curr[i][1];
		}
		//printf("%f ", out_curr[i][1]);
	}

	printf("\n");
#ifdef SHOW_ON_NCURSES
	show_spectrum();
	//usleep(20000);
#else

#endif
#endif

#if AUDIO_PLAY
	// 9. 写音频数据到PCM设备
	while((ret = snd_pcm_writei(playback_handle, pcm_buffer, frames)) < 0)
	{
		usleep(2000);
		if (ret == -EPIPE)
		{
			/* EPIPE means underrun */
			fprintf(stderr, "underrun occurred\n");
			//完成硬件参数设置，使设备准备好
			snd_pcm_prepare(playback_handle);
		} 
		else if (ret < 0) 
		{
			fprintf(stderr, "error from writei: %s\n", snd_strerror(ret));
		}  
	}
#endif

	return MAD_FLOW_CONTINUE;
}

/*
 * This is the error callback function. It is called whenever a decoding
 * error occurs. The error is indicated by stream->error; the list of
 * possible MAD_ERROR_* errors can be found in the mad.h (or stream.h)
 * header file.
 */
static enum mad_flow error(void *data, struct mad_stream *stream, struct mad_frame *frame)
{
	struct buffer *buffer = data;

	fprintf(stderr, "decoding error 0x%04x (%s) at byte offset %lu\n",
	stream->error, mad_stream_errorstr(stream),
	stream->this_frame - buffer->start);

	/* return MAD_FLOW_BREAK here to stop decoding (and propagate an error) */
	return MAD_FLOW_STOP;//MAD_FLOW_CONTINUE;
}

/*
 * This is the function called by main() above to perform all the decoding.
 * It instantiates a decoder object and configures it with the input,
 * output, and error callback functions above. A single call to
 * mad_decoder_run() continues until a callback function returns
 * MAD_FLOW_STOP (to stop decoding) or MAD_FLOW_BREAK (to stop decoding and
 * signal an error).
 */
static int decode(unsigned char const *start, unsigned long length)
{
	struct buffer buffer;
	struct mad_decoder decoder;
	int result;

	/* initialize our private message structure */
	buffer.start  = start;
	buffer.length = length;
	
	printf("Buffer size:%ld\n", buffer.length);

	/* configure input, output, and error functions */
	mad_decoder_init(&decoder, &buffer, input, header_info /* header */, 0 /* filter */, output, error, 0 /* message */);

	/* start decoding */
	result = mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);

	/* release the decoder */
	mad_decoder_finish(&decoder);

	return result;
}
